#include "golbal.h"

void socket_real_ip(NNFW_SOCKET *s) {
	int size, ret;
	const char v2sig[] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";

	do {
		ret = recv(s->sock, &hdr, sizeof(hdr), MSG_PEEK);
	} while (ret == -1 && errno == EINTR);

	if (ret >= (16 + ntohs(hdr.v2.len)) &&
		memcmp(&hdr.v2, v2sig, 12) == 0 &&
		((hdr.v2.ver_cmd & 0xF0) == 0x20) &&
		hdr.v2.fam == 0x11) {
		// we received a proxy v2 header
		inet_ntop(AF_INET, &hdr.v2.addr.ip4.src_addr, s->ip, 64);
		s->port = ntohs(hdr.v2.addr.ip4.src_port);

		// we need to consume the appropriate amount of data from the socket
		// read the buffer without PEEK'ing so that we begin at the real data later in socket_nextjson
		size = 16 + ntohs(hdr.v2.len);
		do {
			ret = recv(s->sock, &hdr, size, 0);
		} while (ret == -1 && errno == EINTR);
		return;
	}
	else {
		// not received any proxy header
		struct sockaddr_in name;
		socklen_t len = sizeof(name);
		memset(&name, 0, len);

		int res = getpeername(s->sock, (struct sockaddr *)&name, &len);
		inet_ntop(AF_INET, &name.sin_addr, s->ip, 64);

		res = getsockname(s->sock, (struct sockaddr *)&name, &len);
		s->port = ntohs(name.sin_port);
		return;
	}
}

NNFW_SOCKET *socket_initialize(int sock) {
	struct timeval timeout;
	timeout.tv_sec = 600;
	timeout.tv_usec = 0;
	NNFW_SOCKET *s = new NNFW_SOCKET;
	memset(s, 0, sizeof(NNFW_SOCKET));
	s->buflen = 0;
	s->sock = sock;
	setsockopt(s->sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
	if (true) {									
		int res = 0;
		struct sockaddr_in name;
		socklen_t len = sizeof(name);
		memset(&name, 0, len);
		res = getpeername(s->sock, (struct sockaddr *)&name, &len);
		inet_ntop(AF_INET, &name.sin_addr, s->ip, 64);
		res = getsockname(s->sock, (struct sockaddr *)&name, &len);
		s->port = ntohs(name.sin_port);
	} else {
		socket_real_ip(s);
	}
	return s;
}
void socket_close(NNFW_SOCKET *s) {
	if(!s) return;
	if(s->sock) close(s->sock);
	s->sock = 0;
	delete s;
}
bool socket_connected(NNFW_SOCKET *s) {
	return s->sock > 0;
}

int socket_send_raw(NNFW_SOCKET *s, const char *buffer, int size) {
	int res = send(s->sock, buffer, size, MSG_NOSIGNAL);
	return res;
}
int socket_send(NNFW_SOCKET *s, const char *format, ...) {
	char buffer[NNFW_SMALLBUFSIZE];
	va_list args;
	va_start(args, format);
	vsprintf(buffer, format, args);
	va_end(args);
	if(!s) {
		errno = EINVAL;
		return -1;
	}
//	pthread_mutex_lock(&s->mutex);
	int res = socket_send_raw(s, buffer, strlen(buffer));
//	pthread_mutex_unlock(&s->mutex);
	return res;
}

bool g_socketExiting  = false;
void *socketHandle(void *p);
void socket_thread() {
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);      //AF_INET: Ipv4网络协议
    if(listen_sock <= 0) { printf("socket error"); return; }
    int optval = 1;
	setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);  
    struct sockaddr_in serv;
	serv.sin_family = AF_INET;
	serv.sin_addr.s_addr = htonl(INADDR_ANY);
	serv.sin_port = htons(3333); 
	int res = bind(listen_sock, (struct sockaddr*)&serv, sizeof(serv));
	if(res < 0) { printf("bind error"); return; }
	res = listen(listen_sock, 4096);
	if(res < 0) { printf("listen error"); return; }

    int failcount   = 0;
	while(!g_socketExiting)
	{
        printf("socket in, wait... \n");
        int sock = accept(listen_sock, NULL, NULL);     //从等待连接队列中抽取第一个连接, 返回句柄
		if(sock <= 0) {
			int error = errno;
			printf("socket accept() error %d\n", error);
			failcount++;
			usleep(50000);
			if (error == 24 && failcount > 5) {
				g_socketExiting = true;   // happen when max open files is reached (see ulimit)
				printf("too much socket failure, exiting...\n");
				exit(error);
			}
			continue;
		}

        failcount = 0;
		pthread_t thread;
		int res = pthread_create(&thread, NULL, socketHandle, (void *)(long)sock);
		if(res != 0) {
			int error = errno;
			close(sock);
			g_socketExiting = true;
			printf("pthread_create error %d %d\n", res, error);
		}
		pthread_detach(thread);
    }
}
